package model;

/**
 * GameBoard.java
 * Purpose: the game board
 * 
 * @author Hans Brems
 * @version 1.0 01/12/2011
 */

import java.awt.Color;
import java.awt.Graphics2D;
import java.util.Stack;

import controller.IGameContext;

public class GameBoard implements IGameContext {
	/* The currently selected cell */
	private Cell activeCell;
	/* Move history (can be undone) */
	private Stack<Move> moves;
	/* Last move (to be drawn) */
	private Move lastMove;
	/* Pieces to be animated over time */
	private MovingPiece movingPiece;
	private FadingPiece fadingPiece;
	/* Game board array */
	private int[][] level;
	
	/** Constructs a game board */
	public GameBoard() {
		this.moves = new Stack<Move>();
	}
	
	/* Gets the value for given cell */
	public int getCellValue(Cell cell) {
		return this.level[cell.row][cell.col];
	}
	
	/** Gets the value for given row and column */
	public int getCellValue(int row, int col) {
		return this.level[row][col];
	}
	
	/* Sets the value for given row and column */
	private void setCellValue(int row, int col, int value) {
		this.level[row][col] = value;
	}
	
	/* Sets the value for given row and column */
	private void setCellValue(Cell cell, int value) {
		this.level[cell.row][cell.col] = value;
	}
	
	/** Returns the length of the columns */
	public int getColLength() {
		return this.level[0].length;
	}
	
	/** Returns the length of the rows */
	public int getRowLength() {
		return this.level.length;
	}
	
	/** Sets the level used by the game board */
	public void setLevel(int[][] level) {
		this.level = level;
	}
	
	/** Returns whether a piece has been selected */
	public boolean hasSelection() {
		return this.activeCell != null;
	}
	
	/** Allows for selection of piece located on given row and column */
	public void setSelection(int row, int col) {
		if (this.getCellValue(row, col) == 1) {
			this.clearSelection();
			this.setCellValue(row, col, 2);
			this.activeCell = new Cell(row, col);
		}
	}
	
	/** Clears the current selection */
	public void clearSelection() {
		if (this.hasSelection()) {
			this.setCellValue(activeCell, 1);
			this.activeCell = null;
		}
	}
	
	/**
	 * Moves the currently selected piece to a destination row and column.
	 * The algorithm looks for a common neighbor between origin and destination.
	 * The algorithm will only compare opposite offsets. A neighbor that is located
	 * south of the selected cell, should only be matched against a neighbor that is
	 * located north of the destination cell.
	 */
	public void moveSelectedPieceTo(int row, int col) {
		int rOff; // Row offset
		int cOff; // Column offset
		Cell oriN; // Neighbor of active (origin) cell
		Cell desN; // Neighbor of empty (destination) cell

		// Browse neighboring rows
		for (rOff = -1; rOff < 2; rOff++) {
			
			// Browse neighboring columns
			for (cOff = -1; cOff < 2; cOff++) {
				
				// Either row or column should be equal to 0 (mutually exclusive)
				if (rOff == 0 ^ cOff == 0) {
					
					// Determine the neighbor of the originating cell
					oriN = new Cell(this.activeCell.row + rOff, this.activeCell.col + cOff);
					
					// Determine the neighbor of the destination cell (opposite end)
					desN = new Cell(row + (rOff * -1), col + (cOff * -1));
					
					// Check if the neighboring cells are equal
					if (oriN.equals(desN)) {
						
						// Make sure the neighboring cell contains a piece
						if (this.getCellValue(oriN) == 1) {
							
							// At this point we have determined a valid move
							Move move = new Move(this.activeCell, new Cell(row, col), oriN);
							this.actionMove(move);
							return;
						}
					}
				}
			}
		}
	}
	
	/* Performs the move action after validation */
	private void actionMove(Move move) {
		// Set neighbor to empty
		this.setCellValue(move.jump, 0);
		this.setCellValue(move.from, 0);
		// Add move to history
		this.lastMove = move;
		this.moves.push(move);
		// Move animation
		Vector movePosition = new Vector(move.from.col * Piece.width, move.from.row * Piece.height);
		Vector moveDestination = new Vector(move.to.col * Piece.width, move.to.row * Piece.height);
		this.movingPiece = new MovingPiece(movePosition, moveDestination);
		// Fade animation
		Vector fadePosition = new Vector(move.jump.col * Piece.width, move.jump.row * Piece.height);
		this.fadingPiece = new FadingPiece(fadePosition);
	}
	
	/** Undo the last move */
	public void undo() {
		Move move;
		// Check if history is not empty
		if (this.moves.empty() != true) {
			// Clear current selection
			this.clearSelection();
			// Last move gets deleted from history
			move = this.moves.pop();
			// Reset the origin cell
			this.setCellValue(move.from, 1);
			// Reset the neighbor
			this.setCellValue(move.jump, 1);
			// Reset the destination cell
			this.setCellValue(move.to, 0);
		}
	}
	
	/** Returns a string representation of the game board */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (int row = 0; row < this.level.length; row++) {
			for (int col = 0; col < this.level.length; col++) {
					sb.append(this.getCellValue(row, col));
			}
			sb.append("\n");
		}
		return sb.toString();
	}
	
	/** Updates the game board */
	public void update(double delta) {
		if (this.movingPiece != null) {
			this.movingPiece.update(delta);
			if (this.movingPiece.isAtDestination()) {
				// Set destination cell to selected
				this.setCellValue(this.lastMove.to, 2);
				this.activeCell = this.lastMove.to;
				this.movingPiece = null;
			}
		}
		
		if (this.fadingPiece != null) {
			this.fadingPiece.update(delta);
			if (this.fadingPiece.hasFaded()) {
				this.setCellValue(this.lastMove.jump, 0);
				this.fadingPiece = null;
			}
		}
	}
	
	/** Draws the game board */
	public void draw(Graphics2D g) {
		for (int row = 0; row < this.getRowLength(); row++) {
			for (int col = 0; col < this.getColLength(); col++) {
				// Get piece coordinates
				int x = col * Piece.width;
				int y = row * Piece.height;
				// Draw piece
				Piece piece = PieceFactory.createPiece(this.getCellValue(row, col));
				piece.setPosition(new Vector(x, y));
				piece.draw(g);
				// Draw location
				String loc = String.format("[%d:%d]", row, col);
				g.setColor(Color.RED);
				g.drawString(loc, x + Piece.width / 4, y + Piece.height / 2);
				// Draw animation
				if (this.movingPiece != null) {
					this.movingPiece.draw(g);
				}
				if (this.fadingPiece != null) {
					this.fadingPiece.draw(g);
				}
			}
		}
	}

	/* Class to store move information */
	private class Move {
		private Cell from;
		private Cell to;
		private Cell jump;
		
		/* Constructs a new game board move */
		public Move(Cell from, Cell to, Cell jump) {
			this.from = from;
			this.to = to;
			this.jump = jump;
		}
	}
	
	/* Class to store position information */
	private class Cell {
		private int row;
		private int col;
		
		/* Constructs a new game board position */
		public Cell(int row, int col) {
			this.row = row;
			this.col = col;
		}
		
		/* Checks if two positions are equal */
		@Override
		public boolean equals(Object o) {
			Cell otherCell = (Cell)o;
			return this.row == otherCell.row &&
					this.col == otherCell.col;
		}
	}
}